package me.zhanghai.ohos.materialratingbar.vector;

import me.zhanghai.ohos.materialratingbar.Log;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.Component;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.global.resource.solidxml.Attribute;
import ohos.global.resource.solidxml.Node;
import ohos.global.resource.solidxml.SolidXml;
import ohos.global.resource.solidxml.Theme;
import ohos.media.image.PixelMap;
import ohos.media.image.common.AlphaType;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.utils.LightweightMap;

import java.io.IOException;
import java.util.*;

import static ohos.agp.utils.Color.alpha;

public class VectorDrawableCompat extends VectorDrawableCommon {
    static final String LOGTAG = "VectorDrawableCompat";

    static final BlendMode DEFAULT_TINT_MODE = BlendMode.SRC_IN;

    private static final String SHAPE_CLIP_PATH = "clip-path";
    private static final String SHAPE_GROUP = "group";
    private static final String SHAPE_PATH = "path";
    private static final String SHAPE_VECTOR = "vector";

    private static final String LINECAP_BUTT = "butt";
    private static final String LINECAP_ROUND = "round";
    private static final String LINECAP_SQUARE = "square";

    private static final String LINEJOIN_MITER = "miter";
    private static final String LINEJOIN_ROUND = "round";
    private static final String LINEJOIN_BEVEL = "bevel";

    // Cap the bitmap size, such that it won't hurt the performance too much
    // and it won't crash due to a very large scale.
    // The drawable will look blurry above this size.
    private static final int MAX_CACHED_BITMAP_SIZE = 2048;

    private static final boolean DBG_VECTOR_DRAWABLE = false;

    private final VectorDrawableCompatState mVectorState;

    private ColorFilter mTintFilter;
    private ColorFilter mColorFilter;

    private boolean mMutated;

    // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise,
    // caching the bitmap by default is allowed.
    private boolean mAllowCaching = true;

    // The Constant state associated with the <code>mDelegateDrawable</code>.
    private ConstantState mCachedConstantStateDelegate;

    // Temp variable, only for saving "new" operation at the draw() time.
    private final float[] mTmpFloats = new float[9];
    private final Matrix mTmpMatrix = new Matrix();
    private final Rect mTmpBounds = new Rect();

    private Component mComponent;

    VectorDrawableCompat() {
        mVectorState = new VectorDrawableCompatState();
    }

    VectorDrawableCompat( VectorDrawableCompatState state) {
        mVectorState = state;
        mTintFilter = new ColorFilter(state.mTint.getValue(),state.mTintMode);
    }

    Object getTargetByName(String name) {
        return mVectorState.mVPathRenderer.mVGTargetsMap.get(name);
    }

    @Override
    public void drawToCanvas(Canvas canvas) {
        // We will offset the bounds for drawBitmap, so copyBounds() here instead
        // of getBounds().
        mTmpBounds.set(getBounds().left,getBounds().top,getBounds().right,getBounds().bottom);
        if (mTmpBounds.getWidth() <= 0 || mTmpBounds.getHeight() <= 0) {
            // Nothing to draw
            return;
        }

        // Color filters always override tint filters.

        final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);

        // The imageView can scale the canvas in different ways, in order to
        // avoid blurry scaling, we have to draw into a bitmap with exact pixel
        // size first. This bitmap size is determined by the bounds and the
        // canvas scale.
        canvas.getMatrix(mTmpMatrix);
        mTmpMatrix.getElements(mTmpFloats);
        float canvasScaleX = Math.abs(mTmpFloats[0]);
        float canvasScaleY = Math.abs(mTmpFloats[4]);

        float canvasSkewX = Math.abs(mTmpFloats[1]);
        float canvasSkewY = Math.abs(mTmpFloats[3]);

        // When there is any rotation / skew, then the scale value is not valid.
        if (canvasSkewX != 0 || canvasSkewY != 0) {
            canvasScaleX = 1.0f;
            canvasScaleY = 1.0f;
        }

        int scaledWidth = (int) (mTmpBounds.getWidth() * canvasScaleX);
        int scaledHeight = (int) (mTmpBounds.getHeight() * canvasScaleY);
        scaledWidth = Math.min(MAX_CACHED_BITMAP_SIZE, scaledWidth);
        scaledHeight = Math.min(MAX_CACHED_BITMAP_SIZE, scaledHeight);

        if (scaledWidth <= 0 || scaledHeight <= 0) {
            return;
        }
        final int saveCount = canvas.save();
        canvas.translate(mTmpBounds.left, mTmpBounds.top);

        // Handle RTL mirroring.
        final boolean needMirroring = needMirroring();
        if (needMirroring) {
            canvas.translate(mTmpBounds.getWidth(), 0);
            canvas.scale(-1.0f, 1.0f);
        }

        // At this point, canvas has been translated to the right position.
        // And we use this bound for the destination rect for the drawBitmap, so
        // we offset to (0, 0);
        mTmpBounds.offset(0, 0);

        mVectorState.createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
        if (!mAllowCaching) {
            mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
        } else {
            if (!mVectorState.canReuseCache()) {
                mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
                mVectorState.updateCacheStates();
            }
        }
        mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter, mTmpBounds);
        canvas.restoreToCount(saveCount);
    }

    @Override
    public int getAlpha() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.getAlpha();
        }

        return mVectorState.mVPathRenderer.getRootAlpha();
    }

    @Override
    public void setAlpha(int alpha) {
        if (mDelegateDrawable != null) {
            mDelegateDrawable.setAlpha(alpha);
            return;
        }

        if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
            mVectorState.mVPathRenderer.setRootAlpha(alpha);
            invalidateSelf();
        }
    }

    public void setComponent(Component mComponent) {
        this.mComponent = mComponent;
    }

    public void invalidateSelf(){
        if (mComponent != null){
            mComponent.invalidate();
        }
    }

    public void setColorFilter(ColorFilter colorFilter) {
        mColorFilter = colorFilter;
    }
    public void setColorFilterInvalidateSelf(ColorFilter colorFilter) {
        mColorFilter = colorFilter;
        invalidateSelf();
    }

    public ColorFilter getColorFilter() {
        return mColorFilter;
    }

    @Override
    public boolean isStateful() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.isStateful();
        }

        return super.isStateful() || (mVectorState != null
                && (mVectorState.isStateful()
                || (mVectorState.mTint != null)));
    }

    @Override
    public int getWidth() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.getWidth();
        }

        return (int) mVectorState.mVPathRenderer.mBaseWidth;
    }

    @Override
    public int getHeight() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.getHeight();
        }

        return (int) mVectorState.mVPathRenderer.mBaseHeight;
    }

    /**
     * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. This
     * is used to calculate the path animation accuracy.
     *
     * @return PixelSize
     */

    public float getPixelSize() {
        if (mVectorState == null || mVectorState.mVPathRenderer == null
                || mVectorState.mVPathRenderer.mBaseWidth == 0
                || mVectorState.mVPathRenderer.mBaseHeight == 0
                || mVectorState.mVPathRenderer.mViewportHeight == 0
                || mVectorState.mVPathRenderer.mViewportWidth == 0) {
            return 1; // fall back to 1:1 pixel mapping.
        }
        float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth;
        float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight;
        float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth;
        float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight;
        float scaleX = viewportWidth / intrinsicWidth;
        float scaleY = viewportHeight / intrinsicHeight;
        return Math.min(scaleX, scaleY);
    }

    /**
     * Create a VectorDrawableCompat object.
     *
     * @param context   the resources.
     * @param resId the resource ID for VectorDrawableCompat object.
     * @return a new VectorDrawableCompat or null if parsing error is found.
     */

    public static VectorDrawableCompat create(Context context,  int resId) {
        try {
            SolidXml solidXml = context.getResourceManager().getSolidXml(resId);
            Node node = solidXml.getRoot();
            if (node != null){
               return createFromXmlInner(context, node);
            }
        } catch (IOException e) {
            Log.e(LOGTAG, "IOException error", e);
        } catch (NotExistException e) {
            Log.e(LOGTAG, "NotExistException error", e);
        } catch (WrongTypeException e) {
            Log.e(LOGTAG, "WrongTypeException error", e);
        }
        return null;
    }

    /**
     * Create a VectorDrawableCompat from inside an XML document using an optional
     * {@link Theme}. Called on a parser positioned at a tag in an XML
     * document, tries to create a Drawable from that tag. Returns {@code null}
     * if the tag is not a valid drawable.
     *
     * @param context context
     * @param parser parser
     *
     * @return Element
     *
     * @throws NotExistException NotExistException
     * @throws IOException IOException
     * @throws WrongTypeException WrongTypeException
     */
    public static VectorDrawableCompat createFromXmlInner(Context context, Node parser) throws NotExistException, IOException,WrongTypeException {
        VectorDrawableCompat drawable = new VectorDrawableCompat();
        drawable.inflate(context, parser);
        return drawable;
    }

    static int applyAlpha(int color, float alpha) {
        int alphaBytes = alpha(color);
        color &= 0x00FFFFFF;
        color |= ((int) (alphaBytes * alpha)) << 24;
        return color;
    }


    public void inflate(Context context,Node parser) throws NotExistException, IOException,WrongTypeException {

        inflate(context,parser,null);
    }


    public void inflate(Context context,Node parser,Theme theme) throws NotExistException, IOException,WrongTypeException {
        final VectorDrawableCompatState state = mVectorState;
        final VPathRenderer pathRenderer = new VPathRenderer();
        state.mVPathRenderer = pathRenderer;

        updateStateFromTypedArray(context,parser);

        state.mCacheDirty = true;

        inflateInternal(context,parser);
    }


    /**
     * Parses a {@link BlendMode} from a tintMode
     * attribute's enum value.
     *
     * @param value value
     * @param defaultMode default value
     *
     * @return         BlendMode
     */
    private static BlendMode parseTintModeCompat(int value, BlendMode defaultMode) {
        switch (value) {
            case 3:
                return BlendMode.SRC_OVER;
            case 5:
                return BlendMode.SRC_IN;
            case 9:
                return BlendMode.SRC_ATOP;
            case 14:
                return BlendMode.MULTIPLY;
            case 15:
                return BlendMode.SCREEN;
            case 16:
                return BlendMode.PLUS;
            default:
                return defaultMode;
        }
    }
    public static boolean isDigitsOnly(CharSequence str) {
        final int len = str.length();
        for (int cp, i = 0; i < len; i += Character.charCount(cp)) {
            cp = Character.codePointAt(str, i);
            if (!Character.isDigit(cp)) {
                return false;
            }
        }
        return true;
    }
    public static int digitIndex(CharSequence str){
        final int len = str.length();
        for (int cp, i = 0; i < len; i += Character.charCount(cp)) {
            cp = Character.codePointAt(str, i);
            if (!Character.isDigit(cp)) {
                return i;
            }
        }
        return -1;
    }
    public static float vpTopx(Context context,CharSequence str){
        final int len = str.length();
        final int index = digitIndex(str);
        if (index > 0){
            int start = Integer.parseInt(str.subSequence(0,index).toString());
            String end = str.subSequence(index,len).toString();
            if (end.equals("vp")){
                return AttrHelper.vp2px(start,context);
            }
        }
        return 0;
    }

    private void updateStateFromTypedArray(Context context,Node parser)
            throws NotExistException, IOException,WrongTypeException {
        final VectorDrawableCompatState state = mVectorState;
        final VPathRenderer pathRenderer = state.mVPathRenderer;

        // Account for any configuration changes.
        // state.mChangingConfigurations |= Utils.getChangingConfigurations(a);

        List<Attribute> attributes = parser.getAttributes();

        if (attributes.size() > 0){
            for (Attribute attribute : attributes){
                switch (attribute.getName()) {
                    case "tintMode":
                        final int mode = Integer.parseInt(attribute.getStringValue());
                        state.mTintMode = parseTintModeCompat(mode, BlendMode.SRC_IN);
                        break;
                    case "tint":
                        state.mTint = new Color(parseColor(attribute.getStringValue()));
                        break;
                    case "autoMirrored":
                        state.mAutoMirrored = Boolean.parseBoolean(attribute.getStringValue());
                        break;
                    case "viewportWidth":
                        pathRenderer.mViewportWidth = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "viewportHeight":
                        pathRenderer.mViewportHeight = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "width":
                        pathRenderer.mBaseWidth = vpTopx(context,attribute.getStringValue());
                        break;
                    case "height":
                        pathRenderer.mBaseHeight = vpTopx(context,attribute.getStringValue());
                        break;
                    case "alpha":
                        pathRenderer.setAlpha(Float.parseFloat(attribute.getStringValue()));
                        break;
                    case "name":
                        final String name = attribute.getStringValue();
                        pathRenderer.mRootName = name;
                        pathRenderer.mVGTargetsMap.put(name, pathRenderer);
                        break;
                    default:
                        break;
                }
            }
        }

        if (pathRenderer.mViewportWidth <= 0) {
            throw new NotExistException("<vector> tag requires viewportWidth > 0");
        } else if (pathRenderer.mViewportHeight <= 0) {
            throw new NotExistException("<vector> tag requires viewportHeight > 0");
        }

        if (pathRenderer.mBaseWidth <= 0) {
            throw new NotExistException(
                    "<vector> tag requires width > 0");
        } else if (pathRenderer.mBaseHeight <= 0) {
            throw new NotExistException(
                    "<vector> tag requires height > 0");
        }

    }

    private boolean noPathTag = true;
    private void inflateInternal(Context context,Node parser)
            throws NotExistException, IOException,WrongTypeException {
        final VectorDrawableCompatState state = mVectorState;
        final VPathRenderer pathRenderer = state.mVPathRenderer;

        // Use a stack to help to build the group tree.
        // The top of the stack is always the current group.
        final ArrayDeque<VGroup> groupStack = new ArrayDeque<>();
        groupStack.push(pathRenderer.mRootGroup);

//        int eventType = parser.getEventType();
//        final int innerDepth = parser.getDepth() + 1;

       parserNode(parser,groupStack,pathRenderer,state);

        // Print the tree out for debug.
        if (DBG_VECTOR_DRAWABLE) {
            printGroupTree(pathRenderer.mRootGroup, 0);
        }

        if (noPathTag) {
            throw new NotExistException("no " + SHAPE_PATH + " defined");
        }
    }


    private void parserNode(Node node,
                            ArrayDeque<VGroup> groupStack,
                            VPathRenderer pathRenderer,
                            VectorDrawableCompatState state){
        final VGroup currentGroup = groupStack.peek();
        switch (node.getName()) {
            case SHAPE_PATH: {
                final VFullPath path = new VFullPath();
                path.inflate(node);
                currentGroup.mChildren.add(path);
                if (path.getPathName() != null) {
                    pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
                }
                noPathTag = false;
                state.mChangingConfigurations |= path.mChangingConfigurations;
                break;
            }
            case SHAPE_CLIP_PATH: {
                final VClipPath path = new VClipPath();
                path.inflate(node);
                currentGroup.mChildren.add(path);
                if (path.getPathName() != null) {
                    pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
                }
                state.mChangingConfigurations |= path.mChangingConfigurations;
                break;
            }
            case SHAPE_GROUP:
                VGroup newChildGroup = new VGroup();
                newChildGroup.inflate(node);
                currentGroup.mChildren.add(newChildGroup);
                groupStack.push(newChildGroup);
                if (newChildGroup.getGroupName() != null) {
                    pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
                            newChildGroup);
                }
                state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
                break;
            default:
                break;
        }
        Node sibling = node.getSibling();
        Node child = node.getChild();
        if (child != null){
            parserNode(child,groupStack,pathRenderer,state);
        }
        if (sibling != null){
            parserNode(sibling,groupStack,pathRenderer,state);
        }


    }

    private void printGroupTree(VGroup currentGroup, int level) {
        String indent = "";
        for (int i = 0; i < level; i++) {
            indent += "    ";
        }
        // Print the current node
        // Then print all the children groups
        for (int i = 0; i < currentGroup.mChildren.size(); i++) {
            VObject child = currentGroup.mChildren.get(i);
            if (child instanceof VGroup) {
                printGroupTree((VGroup) child, level + 1);
            } else {
                ((VPath) child).printVPath(level + 1);
            }
        }
    }

    void setAllowCaching(boolean allowCaching) {
        mAllowCaching = allowCaching;
    }

    // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+.
    private boolean needMirroring() {
//        if (Build.VERSION.SDK_INT >= 17) {
//            return isAutoMirrored()
//                    && DrawableCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
//        } else {
            return false;
//        }
    }

    @Override
    public void setBounds(Rect bounds) {
        super.setBounds(bounds);
        if (mDelegateDrawable != null) {
            mDelegateDrawable.setBounds(bounds);
        }
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.setVisible(visible, restart);
        }
        return super.setVisible(visible, restart);
    }

    /**
     * Constant state for delegating the creating drawable job for SDK >= 24.
     * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
     * a delegated VectorDrawable instance.
     */
    private static class VectorDrawableDelegateState extends ConstantState {
        private final ConstantState mDelegateState;

        public VectorDrawableDelegateState(ConstantState state) {
            mDelegateState = state;
        }

    }

    private static class VectorDrawableCompatState extends ConstantState {
        int mChangingConfigurations;
        VPathRenderer mVPathRenderer;
        Color mTint = null;
        BlendMode mTintMode = DEFAULT_TINT_MODE;
        boolean mAutoMirrored;

        PixelMap mCachedBitmap;
        int[] mCachedThemeAttrs;
        Color mCachedTint;
        BlendMode mCachedTintMode;
        int mCachedRootAlpha;
        boolean mCachedAutoMirrored;
        boolean mCacheDirty;

        /**
         * Temporary paint object used to draw cached bitmaps.
         */
        Paint mTempPaint;

        // Deep copy for mutate() or implicitly mutate.
        public VectorDrawableCompatState(VectorDrawableCompatState copy) {
            if (copy != null) {
                mChangingConfigurations = copy.mChangingConfigurations;
                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
                if (copy.mVPathRenderer.mFillPaint != null) {
                    mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint);
                }
                if (copy.mVPathRenderer.mStrokePaint != null) {
                    mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint);
                }
                mTint = copy.mTint;
                mTintMode = copy.mTintMode;
                mAutoMirrored = copy.mAutoMirrored;
            }
        }

        public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter,
                                                  Rect originalBounds) {
            // The bitmap's size is the same as the bounds.
            final Paint p = getPaint(filter);
            canvas.drawPixelMapHolderRect(new PixelMapHolder(mCachedBitmap),new RectFloat(originalBounds), p);
        }

        public boolean hasTranslucentRoot() {
            return mVPathRenderer.getRootAlpha() < 255;
        }

        /**
         * getPaint
         * @param filter ColorFilter
         *
         * @return null when there is no need for alpha paint.
         */
        public Paint getPaint(ColorFilter filter) {
//            if (!hasTranslucentRoot() && filter == null) {
//                return null;
//            }

            if (mTempPaint == null) {
                mTempPaint = new Paint();
                mTempPaint.setFilterBitmap(true);
            }
            mTempPaint.setAlpha(mVPathRenderer.getRootAlpha());
            mTempPaint.setColorFilter(filter);
            return mTempPaint;
        }

        public void updateCachedBitmap(int width, int height) {
            mCachedBitmap.writePixels(Color.TRANSPARENT.getValue());
            Canvas tmpCanvas = new Canvas(new Texture(mCachedBitmap));
            mVPathRenderer.draw(tmpCanvas, width, height, null);
        }

        public void createCachedBitmapIfNeeded(int width, int height) {
            if (mCachedBitmap == null || !canReuseBitmap(width, height)) {

                PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
                options.size = new Size(width,height);
                options.pixelFormat = PixelFormat.ARGB_8888;
                options.alphaType = AlphaType.UNPREMUL;
                options.editable = true;
                mCachedBitmap = PixelMap.create(options);
                mCacheDirty = true;
            }

        }

        public boolean canReuseBitmap(int width, int height) {
            if (width == mCachedBitmap.getImageInfo().size.width
                    && height == mCachedBitmap.getImageInfo().size.height) {
                return true;
            }
            return false;
        }

        public boolean canReuseCache() {
            if (!mCacheDirty
                    && mCachedTint == mTint
                    && mCachedTintMode == mTintMode
                    && mCachedAutoMirrored == mAutoMirrored
                    && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) {
                return true;
            }
            return false;
        }

        public void updateCacheStates() {
            // Use shallow copy here and shallow comparison in canReuseCache(),
            // likely hit cache miss more, but practically not much difference.
            mCachedTint = mTint;
            mCachedTintMode = mTintMode;
            mCachedRootAlpha = mVPathRenderer.getRootAlpha();
            mCachedAutoMirrored = mAutoMirrored;
            mCacheDirty = false;
        }

        public VectorDrawableCompatState() {
            mVPathRenderer = new VPathRenderer();
        }

        public boolean isStateful() {
            return mVPathRenderer.isStateful();
        }

        public boolean onStateChanged(int[] stateSet) {
            final boolean changed = mVPathRenderer.onStateChanged(stateSet);
            mCacheDirty |= changed;
            return changed;
        }
    }

    private static class VPathRenderer {
        /* Right now the internal data structure is organized as a tree.
         * Each node can be a group node, or a path.
         * A group node can have groups or paths as children, but a path node has
         * no children.
         * One example can be:
         *                 Root Group
         *                /    |     \
         *           Group    Path    Group
         *          /     \             |
         *         Path   Path         Path
         *
         */
        // Variables that only used temporarily inside the draw() call, so there
        // is no need for deep copying.
        private final Path mPath;
        private final Path mRenderPath;
        private static final Matrix IDENTITY_MATRIX = new Matrix();
        private final Matrix mFinalPathMatrix = new Matrix();

        Paint mStrokePaint;
        Paint mFillPaint;
        private PathMeasure mPathMeasure;

        /////////////////////////////////////////////////////
        // Variables below need to be copied (deep copy if applicable) for mutation.
        private int mChangingConfigurations;
        final VGroup mRootGroup;
        float mBaseWidth = 0;
        float mBaseHeight = 0;
        float mViewportWidth = 0;
        float mViewportHeight = 0;
        int mRootAlpha = 0xFF;
        String mRootName = null;
        Boolean mIsStateful = null;

        final LightweightMap<String, Object> mVGTargetsMap = new LightweightMap<String, Object>();

        public VPathRenderer() {
            mRootGroup = new VGroup();
            mPath = new Path();
            mRenderPath = new Path();
        }

        public void setRootAlpha(int alpha) {
            mRootAlpha = alpha;
        }

        public int getRootAlpha() {
            return mRootAlpha;
        }

        // setAlpha() and getAlpha() are used mostly for animation purpose, since
        // Animator like to use alpha from 0 to 1.
        public void setAlpha(float alpha) {
            setRootAlpha((int) (alpha * 255));
        }

        @SuppressWarnings("unused")
        public float getAlpha() {
            return getRootAlpha() / 255.0f;
        }

        public VPathRenderer(VPathRenderer copy) {
            mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
            mPath = new Path(copy.mPath);
            mRenderPath = new Path(copy.mRenderPath);
            mBaseWidth = copy.mBaseWidth;
            mBaseHeight = copy.mBaseHeight;
            mViewportWidth = copy.mViewportWidth;
            mViewportHeight = copy.mViewportHeight;
            mChangingConfigurations = copy.mChangingConfigurations;
            mRootAlpha = copy.mRootAlpha;
            mRootName = copy.mRootName;
            if (copy.mRootName != null) {
                mVGTargetsMap.put(copy.mRootName, this);
            }
            mIsStateful = copy.mIsStateful;
        }

        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
                                   Canvas canvas, int w, int h, ColorFilter filter) {
            // Calculate current group's matrix by preConcat the parent's and
            // and the current one on the top of the stack.
            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
            // Mi the local matrix at level i of the group tree.
            currentGroup.mStackedMatrix.setMatrix(currentMatrix);

            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);

            // Save the current clip information, which is local to this group.
            canvas.save();

            // Draw the group tree in the same order as the XML file.
            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
                VObject child = currentGroup.mChildren.get(i);
                if (child instanceof VGroup) {
                    VGroup childGroup = (VGroup) child;
                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
                            canvas, w, h, filter);
                } else if (child instanceof VPath) {
                    VPath childPath = (VPath) child;
                    drawPath(currentGroup, childPath, canvas, w, h, filter);
                }
            }

            canvas.restore();
        }

        public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
            // Traverse the tree in pre-order to draw.
            drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter);
        }

        private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
                              ColorFilter filter) {
            final float scaleX = w / mViewportWidth;
            final float scaleY = h / mViewportHeight;
            final float minScale = Math.min(scaleX, scaleY);
            final Matrix groupStackedMatrix = vGroup.mStackedMatrix;

            mFinalPathMatrix.setMatrix(groupStackedMatrix);
            mFinalPathMatrix.postScale(scaleX, scaleY);


            final float matrixScale = getMatrixScale(groupStackedMatrix);
            if (matrixScale == 0) {
                // When either x or y is scaled to 0, we don't need to draw anything.
                return;
            }
            vPath.toPath(mPath);
            final Path path = mPath;

            mRenderPath.reset();

            if (vPath.isClipPath()) {
                mRenderPath.setFillType(vPath.mFillRule == 0 ? Path.FillType.WINDING_ORDER
                        : Path.FillType.EVEN_ODD);
                mRenderPath.addPath(path, mFinalPathMatrix, Path.AddPathMode.APPEND_ADD_PATH_MODE);
                canvas.clipPath(mRenderPath, Canvas.ClipOp.INTERSECT);
            } else {
                VFullPath fullPath = (VFullPath) vPath;
                if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
                    float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
                    float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;

                    if (mPathMeasure == null) {
                        mPathMeasure = new PathMeasure(mPath,false);
                    }
                    mPathMeasure.setPath(mPath, false);

                    float len = mPathMeasure.getLength();
                    start = start * len;
                    end = end * len;
                    path.reset();
                    if (start > end) {
                        mPathMeasure.getSegment(start, len, path, true);
                        mPathMeasure.getSegment(0f, end, path, true);
                    } else {
                        mPathMeasure.getSegment(start, end, path, true);
                    }
                    path.rLineTo(0, 0); // fix bug in measure
                }
                mRenderPath.addPath(path, mFinalPathMatrix, Path.AddPathMode.APPEND_ADD_PATH_MODE);

                if (fullPath.mFillColor != Color.TRANSPARENT.getValue()) {
//                    final ComplexColorCompat fill = fullPath.mFillColor;
                    if (mFillPaint == null) {
                        mFillPaint = new Paint();
                        mFillPaint.setStyle(Paint.Style.FILL_STYLE);
                        mFillPaint.setAntiAlias(true);
                    }

//                    final Paint fillPaint = mFillPaint;
//                    if (fill.isGradient()) {
//                        final Shader shader = fill.getShader();
//                        shader.setLocalMatrix(mFinalPathMatrix);
//                        fillPaint.setShader(shader);
//                        fillPaint.setAlpha(Math.round(fullPath.mFillAlpha * 255f));
//                    } else {
//                        fillPaint.setShader(null);
                    mFillPaint.setAlpha(fullPath.mFillAlpha);
                    mFillPaint.setColor(new Color(fullPath.mFillColor));
//                    }
                    mFillPaint.setColorFilter(filter);
                    mRenderPath.setFillType(fullPath.mFillRule == 0 ? Path.FillType.WINDING_ORDER
                            : Path.FillType.EVEN_ODD);
                    canvas.drawPath(mRenderPath, mFillPaint);
                }

                if (fullPath.mStrokeColor != Color.TRANSPARENT.getValue()) {
//                    final ComplexColorCompat strokeColor = fullPath.mStrokeColor;
                    if (mStrokePaint == null) {
                        mStrokePaint = new Paint();
                        mStrokePaint.setStyle(Paint.Style.STROKE_STYLE);
                        mStrokePaint.setAntiAlias(true);
                    }

                    final Paint strokePaint = mStrokePaint;
                    if (fullPath.mStrokeLineJoin != null) {
                        strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
                    }

                    if (fullPath.mStrokeLineCap != null) {
                        strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
                    }

                    strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
//                    if (strokeColor.isGradient()) {
//                        final Shader shader = strokeColor.getShader();
//                        shader.setLocalMatrix(mFinalPathMatrix);
//                        strokePaint.setShader(shader);
//                        strokePaint.setAlpha(Math.round(fullPath.mStrokeAlpha * 255f));
//                    } else {
//                        strokePaint.setShader(null);
//                        strokePaint.setAlpha(255);
//                        strokePaint.setColor(new Color(applyAlpha(fullPath.mStrokeColor,
//                                fullPath.mStrokeAlpha)));
                    strokePaint.setAlpha(fullPath.mStrokeAlpha);
                    strokePaint.setColor(new Color(fullPath.mStrokeColor));
//                    }
                    strokePaint.setColorFilter(filter);
                    final float finalStrokeScale = minScale * matrixScale;
                    strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
                    canvas.drawPath(mRenderPath, strokePaint);
                }
            }
        }

        private static float cross(float v1x, float v1y, float v2x, float v2y) {
            return v1x * v2y - v1y * v2x;
        }

        private float getMatrixScale(Matrix groupStackedMatrix) {
            // Given unit vectors A = (0, 1) and B = (1, 0).
            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
            //
            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
            // scale on x and y axis, and take the minimal of these two.
            // For skew case, an unit square will mapped to a parallelogram. And this function will
            // return the minimal height of the 2 bases.
            float[] unitVectors = new float[]{0, 1, 1, 0};
            groupStackedMatrix.mapPoints(unitVectors);
            float scaleX = (float) Math.hypot(unitVectors[0], unitVectors[1]);
            float scaleY = (float) Math.hypot(unitVectors[2], unitVectors[3]);
            float crossProduct = cross(unitVectors[0], unitVectors[1], unitVectors[2],
                    unitVectors[3]);
            float maxScale = Math.max(scaleX, scaleY);

            float matrixScale = 0;
            if (maxScale > 0) {
                matrixScale = Math.abs(crossProduct) / maxScale;
            }
            if (DBG_VECTOR_DRAWABLE) {
                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
            }
            return matrixScale;
        }

        public boolean isStateful() {
            if (mIsStateful == null) {
                mIsStateful = mRootGroup.isStateful();
            }
            return mIsStateful;
        }

        public boolean onStateChanged(int[] stateSet) {
            return mRootGroup.onStateChanged(stateSet);
        }
    }

    private abstract static class VObject {

        /**
         * isStateful
         * @return {@code true} if this {@code VObject} changes based on state, {@code false}
         * otherwise.
         */
        public boolean isStateful() {
            return false;
        }

        /**
         * onStateChanged
         * @param stateSet int[]
         *
         * @return {@code true} if the state change has caused the appearance of this
         * {@code VObject} to change (that is, it needs to be redrawn), otherwise {@code false}.
         */
        public boolean onStateChanged(int[] stateSet) {
            return false;
        }
    }

    private static class VGroup extends VObject {
        // mStackedMatrix is only used temporarily when drawing, it combines all
        // the parents' local matrices with the current one.
        final Matrix mStackedMatrix = new Matrix();

        /////////////////////////////////////////////////////
        // Variables below need to be copied (deep copy if applicable) for mutation.
        final ArrayList<VObject> mChildren = new ArrayList<>();

        float mRotate = 0;
        private float mPivotX = 0;
        private float mPivotY = 0;
        private float mScaleX = 1;
        private float mScaleY = 1;
        private float mTranslateX = 0;
        private float mTranslateY = 0;

        // mLocalMatrix is updated based on the update of transformation information,
        // either parsed from the XML or by animation.
        final Matrix mLocalMatrix = new Matrix();
        int mChangingConfigurations;
        private int[] mThemeAttrs;
        private String mGroupName = null;

        public VGroup(VGroup copy, LightweightMap<String, Object> targetsMap) {
            mRotate = copy.mRotate;
            mPivotX = copy.mPivotX;
            mPivotY = copy.mPivotY;
            mScaleX = copy.mScaleX;
            mScaleY = copy.mScaleY;
            mTranslateX = copy.mTranslateX;
            mTranslateY = copy.mTranslateY;
            mThemeAttrs = copy.mThemeAttrs;
            mGroupName = copy.mGroupName;
            mChangingConfigurations = copy.mChangingConfigurations;
            if (mGroupName != null) {
                targetsMap.put(mGroupName, this);
            }

            mLocalMatrix.setMatrix(copy.mLocalMatrix);

            final ArrayList<VObject> children = copy.mChildren;
            for (int i = 0; i < children.size(); i++) {
                Object copyChild = children.get(i);
                if (copyChild instanceof VGroup) {
                    VGroup copyGroup = (VGroup) copyChild;
                    mChildren.add(new VGroup(copyGroup, targetsMap));
                } else {
                    VPath newPath;
                    if (copyChild instanceof VFullPath) {
                        newPath = new VFullPath((VFullPath) copyChild);
                    } else if (copyChild instanceof VClipPath) {
                        newPath = new VClipPath((VClipPath) copyChild);
                    } else {
                        throw new IllegalStateException("Unknown object in the tree!");
                    }
                    mChildren.add(newPath);
                    if (newPath.mPathName != null) {
                        targetsMap.put(newPath.mPathName, newPath);
                    }
                }
            }
        }

        public VGroup() {

        }

        public String getGroupName() {
            return mGroupName;
        }

        public Matrix getLocalMatrix() {
            return mLocalMatrix;
        }

        public void inflate(Node parser) {

            updateStateFromTypedArray(parser);

        }

        private void updateStateFromTypedArray(Node parser) {
            // Account for any configuration changes.
            // mChangingConfigurations |= Utils.getChangingConfigurations(a);

            // Extract the theme attributes, if any.
            mThemeAttrs = null;

            List<Attribute> attributes = parser.getAttributes();
            if (attributes.size() > 0){
                for (Attribute attribute : attributes){
                    switch (attribute.getName()){
                        case "rotation":
                            mRotate = Float.parseFloat(attribute.getStringValue());
                            break;
                        case "pivotX":
                            mPivotX = Float.parseFloat(attribute.getStringValue());
                            break;
                        case "pivotY":
                            mPivotY = Float.parseFloat(attribute.getStringValue());
                            break;
                        case "scaleX":
                            mScaleX = Float.parseFloat(attribute.getStringValue());
                            break;
                        case "scaleY":
                            mScaleY = Float.parseFloat(attribute.getStringValue());
                            break;
                        case "translateX":
                            mTranslateX = Float.parseFloat(attribute.getStringValue());
                            break;
                        case "translateY":
                            mTranslateY = Float.parseFloat(attribute.getStringValue());
                            break;
                        case "name":
                            mGroupName = attribute.getStringValue();
                            break;
                        default:
                            break;
                    }
                }
            }
            updateLocalMatrix();
        }

        private void updateLocalMatrix() {
            // The order we apply is the same as the
            // RenderNode.cpp::applyViewPropertyTransforms().
            mLocalMatrix.reset();
            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
            mLocalMatrix.postScale(mScaleX, mScaleY);
            mLocalMatrix.postRotate(mRotate, 0, 0);
            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
        }

        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
        @SuppressWarnings("unused")
        public float getRotation() {
            return mRotate;
        }

        @SuppressWarnings("unused")
        public void setRotation(float rotation) {
            if (rotation != mRotate) {
                mRotate = rotation;
                updateLocalMatrix();
            }
        }

        @SuppressWarnings("unused")
        public float getPivotX() {
            return mPivotX;
        }

        @SuppressWarnings("unused")
        public void setPivotX(float pivotX) {
            if (pivotX != mPivotX) {
                mPivotX = pivotX;
                updateLocalMatrix();
            }
        }

        @SuppressWarnings("unused")
        public float getPivotY() {
            return mPivotY;
        }

        @SuppressWarnings("unused")
        public void setPivotY(float pivotY) {
            if (pivotY != mPivotY) {
                mPivotY = pivotY;
                updateLocalMatrix();
            }
        }

        @SuppressWarnings("unused")
        public float getScaleX() {
            return mScaleX;
        }

        @SuppressWarnings("unused")
        public void setScaleX(float scaleX) {
            if (scaleX != mScaleX) {
                mScaleX = scaleX;
                updateLocalMatrix();
            }
        }

        @SuppressWarnings("unused")
        public float getScaleY() {
            return mScaleY;
        }

        @SuppressWarnings("unused")
        public void setScaleY(float scaleY) {
            if (scaleY != mScaleY) {
                mScaleY = scaleY;
                updateLocalMatrix();
            }
        }

        @SuppressWarnings("unused")
        public float getTranslateX() {
            return mTranslateX;
        }

        @SuppressWarnings("unused")
        public void setTranslateX(float translateX) {
            if (translateX != mTranslateX) {
                mTranslateX = translateX;
                updateLocalMatrix();
            }
        }

        @SuppressWarnings("unused")
        public float getTranslateY() {
            return mTranslateY;
        }

        @SuppressWarnings("unused")
        public void setTranslateY(float translateY) {
            if (translateY != mTranslateY) {
                mTranslateY = translateY;
                updateLocalMatrix();
            }
        }

        @Override
        public boolean isStateful() {
            for (int i = 0; i < mChildren.size(); i++) {
                if (mChildren.get(i).isStateful()) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public boolean onStateChanged(int[] stateSet) {
            boolean changed = false;
            for (int i = 0; i < mChildren.size(); i++) {
                changed |= mChildren.get(i).onStateChanged(stateSet);
            }
            return changed;
        }
    }

    /**
     * Common Path information for clip path and normal path.
     */
    private abstract static class VPath extends VObject {
        protected static final int FILL_TYPE_WINDING = 0;
        protected PathParser.PathDataNode[] mNodes = null;
        String mPathName;
        // Default fill rule is winding, or as known as "non-zero".
        int mFillRule = FILL_TYPE_WINDING;
        int mChangingConfigurations;

        public VPath() {
            // Empty constructor.
        }

        public void printVPath(int level) {
            String indent = "";
            for (int i = 0; i < level; i++) {
                indent += "    ";
            }
        }

        public String nodesToString(PathParser.PathDataNode[] nodes) {
            StringBuilder result = new StringBuilder(" ");
            for (PathParser.PathDataNode node : nodes) {
                result.append(node.mType).append(":");
                float[] params = node.mParams;
                for (float param : params) {
                    result.append(param).append(",");
                }
            }
            return result.toString();
        }

        public VPath(VPath copy) {
            mPathName = copy.mPathName;
            mChangingConfigurations = copy.mChangingConfigurations;
            mNodes = PathParser.deepCopyNodes(copy.mNodes);
        }

        public void toPath(Path path) {
            path.reset();
            if (mNodes != null) {
                PathParser.PathDataNode.nodesToPath(mNodes, path);
            }
        }

        public String getPathName() {
            return mPathName;
        }

        public boolean canApplyTheme() {
            return false;
        }

        public boolean isClipPath() {
            return false;
        }

        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
        @SuppressWarnings("unused")
        public PathParser.PathDataNode[] getPathData() {
            return mNodes;
        }

        @SuppressWarnings("unused")
        public void setPathData(PathParser.PathDataNode[] nodes) {
            if (!PathParser.canMorph(mNodes, nodes)) {
                // This should not happen in the middle of animation.
                mNodes = PathParser.deepCopyNodes(nodes);
            } else {
                PathParser.updateNodes(mNodes, nodes);
            }
        }
    }

    /**
     * Clip path, which only has name and pathData.
     */
    private static class VClipPath extends VPath {
        VClipPath() {
            // Empty constructor.
        }

        VClipPath(VClipPath copy) {
            super(copy);
        }

        public void inflate(Node parser) {

            updateStateFromTypedArray(parser);

        }

        private void updateStateFromTypedArray(Node parser) {
            List<Attribute> attributes = parser.getAttributes();
            if (attributes.size() > 0){
                for (Attribute attribute : attributes){
                    switch (attribute.getName()){
                        case "name":
                            mPathName = attribute.getStringValue();
                            break;
                        case "pathData":
                            mNodes = PathParser.createNodesFromPathData(attribute.getStringValue());
                            break;
                        case "fillType":
                            mFillRule = Integer.parseInt(attribute.getStringValue());
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        @Override
        public boolean isClipPath() {
            return true;
        }
    }

    /**
     * Normal path, which contains all the fill / paint information.
     */
    private static class VFullPath extends VPath {
        /////////////////////////////////////////////////////
        // Variables below need to be copied (deep copy if applicable) for mutation.
        private int[] mThemeAttrs;
        int mStrokeColor = Color.TRANSPARENT.getValue();
        float mStrokeWidth = 0;

        int mFillColor = Color.TRANSPARENT.getValue();
        float mStrokeAlpha = 1.0f;
        float mFillAlpha = 1.0f;
        float mTrimPathStart = 0;
        float mTrimPathEnd = 1;
        float mTrimPathOffset = 0;

        Paint.StrokeCap mStrokeLineCap = Paint.StrokeCap.BUTT_CAP;
        Paint.Join mStrokeLineJoin = Paint.Join.MITER_JOIN;
        float mStrokeMiterlimit = 4;

        VFullPath() {
            // Empty constructor.
        }

        VFullPath(VFullPath copy) {
            super(copy);
            mThemeAttrs = copy.mThemeAttrs;

            mStrokeColor = copy.mStrokeColor;
            mStrokeWidth = copy.mStrokeWidth;
            mStrokeAlpha = copy.mStrokeAlpha;
            mFillColor = copy.mFillColor;
            mFillRule = copy.mFillRule;
            mFillAlpha = copy.mFillAlpha;
            mTrimPathStart = copy.mTrimPathStart;
            mTrimPathEnd = copy.mTrimPathEnd;
            mTrimPathOffset = copy.mTrimPathOffset;

            mStrokeLineCap = copy.mStrokeLineCap;
            mStrokeLineJoin = copy.mStrokeLineJoin;
            mStrokeMiterlimit = copy.mStrokeMiterlimit;
        }

        private Paint.StrokeCap getStrokeLineCap(String id, Paint.StrokeCap defValue) {
            switch (id) {
                case LINECAP_BUTT:
                    return Paint.StrokeCap.BUTT_CAP;
                case LINECAP_ROUND:
                    return Paint.StrokeCap.ROUND_CAP;
                case LINECAP_SQUARE:
                    return Paint.StrokeCap.SQUARE_CAP;
                default:
                    return defValue;
            }
        }

        private Paint.Join getStrokeLineJoin(String id, Paint.Join defValue) {
            switch (id) {
                case LINEJOIN_MITER:
                    return Paint.Join.MITER_JOIN;
                case LINEJOIN_ROUND:
                    return Paint.Join.ROUND_JOIN;
                case LINEJOIN_BEVEL:
                    return Paint.Join.BEVEL_JOIN;
                default:
                    return defValue;
            }
        }

        @Override
        public boolean canApplyTheme() {
            return mThemeAttrs != null;
        }

        public void inflate(Node parser) {
            updateStateFromTypedArray(parser);

        }

        private void updateStateFromTypedArray(Node parser) {
            // Account for any configuration changes.
            // mChangingConfigurations |= Utils.getChangingConfigurations(a);

            // Extract the theme attributes, if any.
            mThemeAttrs = null;

            final List<Attribute> attributes = parser.getAttributes();
            if (attributes.size() <= 0){

                return;
            }

            // In order to work around the conflicting id issue, we need to double check the
            // existence of the attribute.
            // B/c if the attribute existed in the compiled XML, then calling TypedArray will be
            // safe since the framework will look up in the XML first.
            // Note that each getAttributeValue take roughly 0.03ms, it is a price we have to pay.

            for (Attribute attribute : attributes){
                Log.e(LOGTAG, "VFullPath updateStateFromTypedArray " + attribute.getName());
                switch (attribute.getName()){
                    case "name":
                        mPathName = attribute.getStringValue();
                        break;
                    case "pathData":
                        mNodes = PathParser.createNodesFromPathData(attribute.getStringValue());
                        break;
                    case "fillColor":
                        mFillColor = parseColor(attribute.getStringValue());
                        Log.e(LOGTAG, "VFullPath updateStateFromTypedArray mFillColor = " + attribute.getStringValue());
                        break;
                    case "fillAlpha":
                        mFillAlpha = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "strokeLineCap":
//                        int lineCap = Integer.parseInt(attribute.getStringValue());
                        mStrokeLineCap = getStrokeLineCap(attribute.getStringValue(), mStrokeLineCap);
                        break;
                    case "strokeLineJoin":
//                        int lineJoin = Integer.parseInt(attribute.getStringValue());
                        mStrokeLineJoin = getStrokeLineJoin(attribute.getStringValue(), mStrokeLineJoin);
                        break;
                    case "strokeMiterLimit":
                        mStrokeMiterlimit = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "strokeColor":
                        mStrokeColor = parseColor(attribute.getStringValue());
                        break;
                    case "strokeAlpha":
                        mStrokeAlpha = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "strokeWidth":
                        mStrokeWidth = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "trimPathEnd":
                        mTrimPathEnd = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "trimPathOffset":
                        mTrimPathOffset = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "trimPathStart":
                        mTrimPathStart = Float.parseFloat(attribute.getStringValue());
                        break;
                    case "fillType":
                        mFillRule = Integer.parseInt(attribute.getStringValue());
                        break;
                    default:
                        break;
                }
            }

        }

        @Override
        public boolean isStateful() {
            return false;
        }

//        @Override
//        public boolean onStateChanged(int[] stateSet) {
//            boolean changed = mFillColor.onStateChanged(stateSet);
//            changed |= mStrokeColor.onStateChanged(stateSet);
//            return changed;
//        }


        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
        @SuppressWarnings("unused")
        int getStrokeColor() {
            return mStrokeColor;
        }

        @SuppressWarnings("unused")
        void setStrokeColor(int strokeColor) {
            mStrokeColor = strokeColor;
        }

        @SuppressWarnings("unused")
        float getStrokeWidth() {
            return mStrokeWidth;
        }

        @SuppressWarnings("unused")
        void setStrokeWidth(float strokeWidth) {
            mStrokeWidth = strokeWidth;
        }

        @SuppressWarnings("unused")
        float getStrokeAlpha() {
            return mStrokeAlpha;
        }

        @SuppressWarnings("unused")
        void setStrokeAlpha(float strokeAlpha) {
            mStrokeAlpha = strokeAlpha;
        }

        @SuppressWarnings("unused")
        int getFillColor() {
            return mFillColor;
        }

        @SuppressWarnings("unused")
        void setFillColor(int fillColor) {
            mFillColor = fillColor;
        }

        @SuppressWarnings("unused")
        float getFillAlpha() {
            return mFillAlpha;
        }

        @SuppressWarnings("unused")
        void setFillAlpha(float fillAlpha) {
            mFillAlpha = fillAlpha;
        }

        @SuppressWarnings("unused")
        float getTrimPathStart() {
            return mTrimPathStart;
        }

        @SuppressWarnings("unused")
        void setTrimPathStart(float trimPathStart) {
            mTrimPathStart = trimPathStart;
        }

        @SuppressWarnings("unused")
        float getTrimPathEnd() {
            return mTrimPathEnd;
        }

        @SuppressWarnings("unused")
        void setTrimPathEnd(float trimPathEnd) {
            mTrimPathEnd = trimPathEnd;
        }

        @SuppressWarnings("unused")
        float getTrimPathOffset() {
            return mTrimPathOffset;
        }

        @SuppressWarnings("unused")
        void setTrimPathOffset(float trimPathOffset) {
            mTrimPathOffset = trimPathOffset;
        }
    }

    public static int parseColor(String colorString) {
        if (colorString.charAt(0) == '#') {
            // Use a long to avoid rollovers on #ffXXXXXX
            long color = Long.parseLong(colorString.substring(1), 16);
            if (colorString.length() == 7) {
                // Set the alpha value
                color |= 0x00000000ff000000;
            } else if (colorString.length() != 9) {
                throw new IllegalArgumentException("Unknown color");
            }
            return (int)color;
        } else {
            Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT));
            if (color != null) {
                return color;
            }
        }
        throw new IllegalArgumentException("Unknown color");
    }
    private static final HashMap<String, Integer> sColorNameMap;
    static {
        sColorNameMap = new HashMap<>();
        sColorNameMap.put("black", 0xFF000000);
        sColorNameMap.put("darkgray", 0xFF444444);
        sColorNameMap.put("gray", 0xFF888888);
        sColorNameMap.put("lightgray", 0xFFCCCCCC);
        sColorNameMap.put("white", 0xFFFFFFFF);
        sColorNameMap.put("red", 0xFFFF0000);
        sColorNameMap.put("green", 0xFF00FF00);
        sColorNameMap.put("blue", 0xFF0000FF);
        sColorNameMap.put("yellow", 0xFFFFFF00);
        sColorNameMap.put("cyan", 0xFF00FFFF);
        sColorNameMap.put("magenta", 0xFFFF00FF);
        sColorNameMap.put("aqua", 0xFF00FFFF);
        sColorNameMap.put("fuchsia", 0xFFFF00FF);
        sColorNameMap.put("darkgrey", 0xFF444444);
        sColorNameMap.put("grey", 0xFF888888);
        sColorNameMap.put("lightgrey", 0xFFCCCCCC);
        sColorNameMap.put("lime", 0xFF00FF00);
        sColorNameMap.put("maroon", 0xFF800000);
        sColorNameMap.put("navy", 0xFF000080);
        sColorNameMap.put("olive", 0xFF808000);
        sColorNameMap.put("purple", 0xFF800080);
        sColorNameMap.put("silver", 0xFFC0C0C0);
        sColorNameMap.put("teal", 0xFF008080);

    }
}
